var Emitter = require('emitter');
var ArrayMgr = require('arraymgr');

var Outlet = require('./outlet');

function Program() {
  Emitter.call(this);
}

Program.prototype = new Emitter();

Program.prototype.init = function(confs) {
  this.classes = new ArrayMgr();
  this.modules = new ArrayMgr();
  this.errors_to = new ArrayMgr();
  this.warnings_to = new ArrayMgr();
  this.confs = confs;
};

Program.prototype.onerror = function(err) {
  this.errors_to.iterate(function(m) {
    var key = 'on_' + m.event;
    m.module[key](err);
  });
};

Program.prototype.onwarning = function(warning) {
  this.warnings_to.iterate(function(m) {
    var key = 'on_' + m.event;
    m.module[key](warning);
  });
};

// Create a module according to a conf
Program.prototype.createModule = function(conf) {
  if (conf.args == null) {
    conf.args = {};
  }
  var className = conf['class'];
	var klass = this.classes[className];
	if (klass == null) {
		throw new Error('no class ' + className + ' found');
	}
	var module = new klass(conf.argvs);
	module.outputs = {};
	return module;
};

// Check if key is reserved. errors_to and warnings_to
// indecates outlets that should receive the program's 
// error and warning events, input and output indecates
// THE outlet that should act as INPUT and OUTPUT in a
// submodule system. each key is optional in conf. if no
// errors_to is presented, the program will just crash
// when a error event is raised up.
Program.prototype.isReserved = function(key) {
	if (key === 'errors_to' || key === 'warnings_to' || key === 'input' || key === 'output') {
		return true;
	}
};

//Creating modules and outlets
Program.prototype.createModulesAndOutlets = function() {
	for (var k in this.confs) {
		if (this.isReserved(k)) {
			continue;
		}
		var conf = this.confs[k];
		var module = this.createModule(conf);
		module.id = k;
		for (var j in conf.outputs) {
			var outlet = new Outlet();
			module.outputs[j] = outlet;
		}
		this.modules[k] = module;
	}
};

// Creating connections
Program.prototype.createConnections = function() {
	for (var k in this.confs) {
		if (this.isReserved(k)) {
			continue;
		}
		var conf = this.confs[k];
		for (var j in conf.outputs) {
			var outlet = conf.outputs[j];
			var from = this.modules[k].outputs[j];
			for (var i = 0, len = outlet.length; i < len; i++) {
				var to_module_id = outlet[i].module;
				var to_outlet_name = outlet[i].outlet;
				var to_module = this.modules[to_module_id];
				if (to_module == null) {
					console.log(to_module_id);
					console.log(to_outlet_name);
					var err_msg = 'bad connection, no outlet for module ' + to_module_id + ', outlet ' + to_outlet_name;
					throw new Error(err_msg);
				}
				from.connectTo(to_module, to_outlet_name);
			}
		}
	}
};

// Bind events for errors_to, warnings_to, input and output
Program.prototype.bindFinals = function() {
	var errors_to = this.confs.errors_to;
	var warnings_to = this.confs.warnings_to;
	var input = this.confs.input;
	var output = this.confs.output;

	if (errors_to != null) {
		for (var i = 0, len = errors_to.length; i < len; i++) {
			var module_id = errors_to[i].module;
			var outlet = errors_to[i].outlet;
			this.errors_to.push({
				module: this.modules[module_id],
				event: outlet
			});
		}
	}

	if (warnings_to != null) {
		for (var i = 0, len = warnings_to.length; i < len; i++) {
			var module_id = warnings_to[i].module;
			var outlet = warnings_to[i].outlet;
			this.warnings_to.push({
				module: this.modules[module_id],
				event: outlet
			});
		}
	}

	// if (input != null) {
	// 	var module_id = input.module;
	// 	var outlet = input.outlet;
	// 	this.input = this.modules[module_id].inputs[outlet];
	// }

	if (output != null) {
		var module_id = output.module;
		var outlet = output.outlet;
		this.output = this.modules[module_id].outputs[outlet];
	}
};

// System ready to go!
// catch module's error and warning event
// and call method main on each module
Program.prototype.start = function() {
	var self = this;
	for (var k in this.modules) {
		var module = this.modules[k];
		module.on('error', function(err) {
			if (typeof err === 'string') {
				err = {
					err: err
				};
			}
			err.module_id = this.id;
			err.time = new Date().getTime();
			self.emit('error', err);
		});
		module.on('warning', function(warning) {
			if (typeof warning === 'string') {
				warning = {
					warning: warning
				};
			}
			warning.module_id = this.id;
			warning.time = new Date().getTime();
			self.emit('warning', warning);
		});
		if (module.main && typeof module.main === 'function') {
			module.main();
		}
	}
};

// Main process
Program.prototype.main = function() {
	this.createModulesAndOutlets();
	this.createConnections();
	this.bindFinals();
	this.start();
};

// Dispose a module
Program.prototype.disposeModule = function(module) {
	module.removeAllListeners();
	if (module.dispose && typeof module.dispose === 'function') {
		module.dispose();
	}
	for (var k in module.outputs) {
		module.outputs[k].dispose();
		module.outputs[k] = null;
	}
	module.outputs = null;
};

Program.prototype.dispose = function() {
	for (var k in this.modules) {
		var module = this.modules[k];
		this.disposeModule(module);
	}
};

module.exports = Program;
